#include <graphics.h>  
#include <conio.h>
#include <vector>
#include <windows.h>
#include <time.h>
using namespace std;
#define  WIDTH 800  
#define  HEIGHT 600

//EasyXPng.h打不开，暂时这样
void putimagePng(int img_x, int img_y, IMAGE* pSrcImg)
{
	DWORD* dst = GetImageBuffer();
	DWORD* src = GetImageBuffer(pSrcImg);
	int src_width = pSrcImg->getwidth();
	int src_height = pSrcImg->getheight();
	int dst_width = getwidth();
	int dst_height = getheight();

	int im_width, im_height;
	if (img_x + src_width > dst_width)
		im_width = dst_width - img_x;
	else
		im_width = src_width;
	if (img_y + src_height > dst_height)
		im_height = dst_height - img_y;
	else
		im_height = src_height;

	if (img_x < 0)
	{
		src = src - img_x;
		im_width = im_width + img_x;
		img_x = 0;
	}
	if (img_y < 0)
	{
		src = src - src_width * img_y;
		im_height = im_height + img_y;
		img_y = 0;
	}
	dst = dst + dst_width * img_y + img_x;

	for (int i = 0; i < im_height; i++)
	{
		for (int j = 0; j < im_width; j++)
		{
			int src_r = ((src[j] & 0xff0000) >> 16);
			int src_g = ((src[j] & 0xff00) >> 8);
			int src_b = src[j] & 0xff;
			int src_a = ((src[j] & 0xff000000) >> 24);
			int dst_r = ((dst[j] & 0xff0000) >> 16);
			int dst_g = ((dst[j] & 0xff00) >> 8);
			int dst_b = dst[j] & 0xff;
			dst[j] = ((src_r + dst_r * (255 - src_a) / 255) << 16)
				| ((src_g + dst_g * (255 - src_a) / 255) << 8)
				| (src_b + dst_b * (255 - src_a) / 255);
		}
		dst = dst + dst_width;
		src = src + src_width;
	}
}

//Timer打不开，暂时这样
class Timer // 改进的延时
{
private:
	LARGE_INTEGER clock;
	LONGLONG lastClock;
	int frequency;

public:
	Timer()
	{
		QueryPerformanceFrequency(&clock);
		frequency = (int)clock.QuadPart / 1000;
		lastClock = 0;
	}

	void Sleep(int ms)
	{
		unsigned int c = ms * frequency;
		if (lastClock == 0)
		{
			QueryPerformanceCounter(&clock);
			lastClock = clock.QuadPart;
		}
		lastClock += c;
		QueryPerformanceCounter(&clock);
		if (clock.QuadPart > lastClock)
			lastClock = clock.QuadPart;
		else
		{
			do
			{
				::Sleep(1);
				QueryPerformanceCounter(&clock);
			} while (clock.QuadPart < lastClock);
		}
	}
};

// 定义函数，随机生成两个整数间的任意整数
int randBetweenMinMax(int min,int max)
{
	int r = rand() % (max-min+1) + min;
	return r;
}


enum rolestatus // 枚举类型，游戏角色所有的可能状态
{
	standleft, standright, runleft, runright, jumpleft, jumpright, liedown, squatdown
};

class Land  // 地面类
{
public:
	IMAGE im_land;  // 地面图像
	float left_x,right_x,top_y; // 用来刻画一块地面的左、右、上坐标
	float land_width,land_height; // 一块地面图像的宽度、高度

	void initialize() // 初始化
	{
		loadimage(&im_land, _T("land.png")); // 导入地面图片
		land_width = im_land.getwidth(); // 获得地面图像的宽、高
		land_height = im_land.getheight();
		left_x = WIDTH/2;  // land初始化在画面正中间，正好就在玩家角色脚底下
		right_x = left_x+land_width;
		top_y = HEIGHT/2;
	}
	void draw(float px,float py)// 显示相关信息	
	{
		putimage(left_x-px,top_y-py,&im_land);  // 角色不动，绘制地面有一个偏移量
	}
};


class Scene // 游戏场景类
{
public:
	IMAGE im_bk;  // 背景图像	
	vector<Land> lands; // 多个地面

	void draw(float px,float py)// 显示相关信息	
	{
		// 角色不动，绘制背景有一个偏移量
		// 背景偏移量/20，就形成了有深度差的前后景的视觉效果
		putimage(-px/20, -100-py/20, &im_bk);	// 显示背景	
		for (int i=0;i<lands.size();i++)
		{
			lands[i].draw(px,py);  // 绘制所有地面
		}
	}

	void initialize() // 初始化
	{
		loadimage(&im_bk,  _T("landscape2.png")); // 导入背景图片

		lands.clear();// 先清空掉vector

		// 第一个land要在游戏角色的正下方
		Land land1;
		land1.initialize();
		lands.push_back(land1);
		for (int i=1;i<20;i++) // 再产生一些随机地面
		{
			Land land2;
			land2.initialize();
			land2.left_x = land1.right_x + rand()%2 * land1.land_width;
			land2.right_x = land2.left_x + land2.land_width;
			land2.top_y = HEIGHT/2 + rand()%3 * HEIGHT/10;
			lands.push_back(land2);
			land1 = land2;
		}
	}
};

class Role
{
public:
	IMAGE im_show;  // 当前时刻要显示的图像
	IMAGE im_standright; // 向右站立图像
	IMAGE im_standleft; // 向左站立图像
	IMAGE im_jumpright; // 向右方向跳跃图像
	IMAGE im_jumpleft; // 向左方向跳跃图像
	vector <IMAGE> ims_runright; // 向右奔跑的图像序列
	vector <IMAGE> ims_runleft; // 向左奔跑的图像序列
	int animId;  // 用于循环动画播放的id

	rolestatus rolestatus; // 当前的状态

	float x_left, y_bottom; // 这两个坐标，因为只要用这两个和地面碰撞就行了
	float vx, vy; // 速度
	float gravity; // 重力加速度
	float width, height; // 图片的宽度、高度

	void draw()//显示相关信息
	{
		putimagePng(WIDTH/2,HEIGHT/2-height, &im_show);
	}

	void initialize() // 初始化
	{
		ims_runleft.clear(); // 先清空掉vector
		ims_runright.clear();
		loadimage(&im_standright, _T("standright.png")); // 导入向右站立图片
		loadimage(&im_standleft, _T("standleft.png")); // 导入向左站立图片
		loadimage(&im_jumpright, _T("jumpright.png")); // 导入向右方向跳跃图像
		loadimage(&im_jumpleft, _T("jumpleft.png")); // 导入向左方向跳跃图像
		

		rolestatus = standright; // 初始为向右站立的游戏状态
		im_show = im_standright;  // 初始显示向右站立的图片
		width = im_standright.getwidth(); // 获得图像的宽、高，所有动画图片大小一样
		height = im_standright.getheight();

		TCHAR filename[80];
		for (int i = 1; i < 3; i++)
		{
			swprintf_s(filename, _T("runright%d.png"), i); //把向右奔跑的两张图片对象添加到ims_runright中
			IMAGE im;
			loadimage(&im, filename);
			ims_runright.push_back(im);
		}
		for (int i = 1; i < 3; i++)
		{
			swprintf_s(filename, _T("runleft%d.png"), i);//把向左奔跑的两张图片对象添加到ims_runleft中
			IMAGE im;
			loadimage(&im, filename);
			ims_runleft.push_back(im);
		}
		



		animId = 0; // 动画id开始设为0

		updateXY(WIDTH / 2, HEIGHT/2 ); // 开始将角色放在画面中间
		vx = 10;//水平方向初速度
		vy = 0;//竖直方向初速度
		gravity = 3;  // 设定重力加速度
	}
	void updateXY(float mx, float my) // 根据输入，更新玩家坐标
	{
		x_left = mx;
		y_bottom = my;
	}
	void runRight(Scene &scene) // 游戏角色向右奔跑
	{
		x_left += vx; // 横坐标增加，向右移动
		if (isNotOnAllLands(scene.lands,vy))  // 移动后不在任何一块地面上了
		{
			im_show = im_jumpright;// 切换到向右起跳图片
			rolestatus = jumpright;// 切换到向右起跳状态
			return;
		}
		if (rolestatus == jumpleft || rolestatus == jumpright) // 如果是起跳状态
		{
			im_show = im_jumpright; // 改变造型为向右起跳造型
		}
		else
		{
			if (rolestatus != runright) // 如果之前角色状态不是向右奔跑
			{
				rolestatus = runright; // 切换为向右奔跑状态
				animId = 0; // 动画播放id初始化为0
			}
			else // 表示之前就是向右奔跑状态了
			{
				animId++; // 动画图片开始切换
				if (animId >= ims_runright.size()) // 循环切换
					animId = 0;
			}
			im_show = ims_runright[animId]; // 设置要显示的对应图片
		}	
	}

	void runLeft(Scene &scene) // 游戏角色向左奔跑
	{
		x_left -= vx; // 横坐标增加，向左移动
		if (isNotOnAllLands(scene.lands,vy))  // 移动后不在任何一块地面上了
		{
			im_show = im_jumpleft; // 切换到向左起跳图片
			rolestatus = jumpleft; // 切换到向左起跳状态
			return;
		}
		if (rolestatus == jumpleft || rolestatus == jumpright) // 如果是起跳状态
		{
			im_show = im_jumpleft; // 改变造型为向左起跳造型
		}
		else
		{
			if (rolestatus != runleft) // 如果之前角色状态不是向左奔跑
			{
				rolestatus = runleft; // 切换为向左奔跑状态
				animId = 0; // 动画播放id初始化为0
			}
			else // 表示之前就是向左奔跑状态了
			{
				animId++; // 动画图片开始切换
				if (animId >= ims_runleft.size()) // 循环切换
					animId = 0;
			}
			im_show = ims_runleft[animId]; // 设置要显示的对应图片
		}	
	}

	void standStill() // 游戏角色默认为向左或向右静止站立
	{
		if (rolestatus == standleft|| rolestatus == runleft)
		{
			im_show = im_standleft;
		}
		else if (rolestatus == standright|| rolestatus == runright)
		{
			im_show = im_standright;
		}
	}
	void beginJump() // 按下s键后，游戏角色跳跃的处理
	{
		if (rolestatus != jumpleft && rolestatus != jumpright) // 已经在空中的话，不要起跳
		{
			if (rolestatus == runleft || rolestatus == standleft)  // 起跳前是向左跑或向左站立状态
			{
				im_show = im_jumpleft; // 切换到向左起跳图片
				rolestatus = jumpleft; // 切换到向左起跳状态
			}
			else if (rolestatus == runright || rolestatus == standright)// 起跳前是向右跑或向右站立状态
			{
				im_show = im_jumpright;// 切换到向右起跳图片
				rolestatus = jumpright;// 切换到向右起跳状态
			}
			vy = -30; // 给角色一个向上的初速度
			
		}
	}


	int isOnLand(Land &land,float ySpeed)  
	{
		float x_right = x_left + width;
		// 判断是否站在砖块上，还需要考虑player的y方向速度情况，速度过快有可能直接穿透地面
		if (ySpeed<=0) // y轴方向速度小于0，表示正在向上运动，不需要考虑速度的影响
			ySpeed = 0;
		if (land.left_x - x_left <= width*0.6 && x_right - land.right_x <= width*0.6 && abs(y_bottom-land.top_y)<=5+ySpeed)
			return 1;
		else
			return 0;
	}

	int isNotOnAllLands(vector<Land> &lands,float speed) // 判断玩家是否不在所有的地面上
	{
		for (int i=0;i<lands.size();i++)
		{
			if (isOnLand(lands[i],speed))
				return 0; // 在任何一块地面上，返回0
		}
		return 1; // 不在所有地面上，返回1
	}

	void updateYcoordinate(Scene &scene) // x坐标是按键盘控制的，而y坐标是每帧自动更新的
	{
		if (rolestatus == jumpleft || rolestatus == jumpright) // 当前是在空中跳跃状态
		{
			vy += gravity; // y方向速度受重力影响变化
			y_bottom += vy;  // y方向位置受速度影响变化

			for (int i=0;i<scene.lands.size();i++)   // 对所有地面遍历
			{
				if (isOnLand(scene.lands[i],vy)) // 当火柴人正好站在一个地面上时
				{
					y_bottom = scene.lands[i].top_y; // 保证正好落在地面上
					if (rolestatus == jumpleft) // 向左跳，落地后切换到向左站立方向
						rolestatus = standleft;
					if (rolestatus == jumpright) // 向右跳，落地后切换到向右站立方向
						rolestatus = standright;
					break; // 跳出循环，不需要再对其他地面判断了
				}
			}
		}
	}
};

// 一些全局变量
Role role;  // 定义玩家控制的游戏角色对象
Timer timer;  // 用于精确延时
Scene scene;  //定义场景全局对象


void startup()  // 初始化
{
	srand(time(0)); // 初始化随机数种子
	scene.initialize(); // 游戏场景初始化
	role.initialize(); // 玩家角色初始化
	initgraph(WIDTH, HEIGHT); // 新开一个画面
	BeginBatchDraw(); // 开始批量绘制
}

void show()  // 显示
{
	scene.draw(role.x_left-WIDTH/2,role.y_bottom-HEIGHT/2);   // 显示场景相关信息
	role.draw();  // 显示玩家相关信息
	FlushBatchDraw(); // 批量绘制
	timer.Sleep(50); // 暂停若干毫秒
}
void updateWithoutInput() // 和输入无关的更新
{
	role.updateYcoordinate(scene); // 游戏角色y坐标是每帧自动更新的
}

void updateWithInput() // 和输入有关的更新
{
	role.standStill(); // 游戏角色默认为向左或向右静止站立
	if (_kbhit())  // 如果按键
	{
		if (GetAsyncKeyState('D'))
		{
			role.runRight(scene);
		}
		else if (GetAsyncKeyState('A'))
		{
			role.runLeft(scene);
		}
		if (GetAsyncKeyState('S'))
		{
			role.beginJump();
		}
		
	}
}
int main() // 主函数
{
	startup();  	// 初始化
	while (1)       // 一直循环
	{
		show();  // 显示
		updateWithoutInput(); // 与输入无关的更新
		updateWithInput();    // 与输入有关的更新
	}
	return 0;
}